#include "StdAfx.h"
#include "AI_Brain.h"
#include "Scene.h"
#include "IEntity.h"
#include "Player.h"


/** Calculate the distance between the two vectors */
float Distance(const Vector2f &v1, const Vector2f &v2)
{
	float x1 = v1.x;
	float x2 = v2.x;
	float y1 = v1.y;
	float y2 = v2.y;

	x1 = x1 - x2;
	y1 = y1 - y2;

	float dist = sqrtf(x1*x1 + y1*y1);
	return sqrtf(x1*x1 + y1*y1);
}

float Distance(PathNode &v1, PathNode &v2)
{
    return Distance( v1.location, v2.location );
}

/**-----------------------------------------------------------
    AI_Brain
--------------------------------------------------------------*/

//constructor
AI_Brain::AI_Brain(void)
{
	EventManager::GetInstance()->AddListener(this);
}

//deconstructor
AI_Brain::~AI_Brain(void)
{
	EventManager::GetInstance()->RemoveListener(this);
}

//
//  NOT WORKING WHEN THIS IS ENABLED!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
//
bool AI_Brain::legalMove(IEntity* entPtr)
{
    // TEMP!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    //Scene* s = EntityManager::GetInstance()->GetScene();
    //if ( s )
    //    return !s->Collide( entPtr->m_world.x,entPtr->m_world.y, entPtr->GetBounds() ) ; 
    //return false;
    
    return true;
}

void AI_Brain::addNeighbor(std::list<PathNode> &openList,std::list<PathNode> &closedList, PathNode &entNode, IEntity* entPtr, const Vector2f &offSet)
{
	std::list<PathNode>::iterator iter;
	bool isClosed = false;

	//move right
	Vector2f entLoc( entNode.location + offSet );
	if( legalMove(entPtr) )
	{
		//check if the closed list is empty
		if(closedList.size() > 0)
			//check if this position has already been visited
			for(iter = closedList.begin(); iter != closedList.end()&&!isClosed; iter++)
			{
				PathNode node = *iter;
				if( node.location == entLoc)
					isClosed = true;
			}
		//if this position hasnt been visited, add it to the queue
		if(!isClosed)
		{
			PathNode node( entLoc );
			node.entityPath = &entNode;
			openList.push_front(node);
		}
	}
}

//Adds the neighbors of the current position into the open list if they are valid choices
void AI_Brain::addNeighbors(std::list<PathNode> &openList,std::list<PathNode> &closedList, PathNode &entNode, IEntity* entPtr)
{
	int eightDirectionsX[] = {-64,   0,  64, 64, 64,  0, -64, -64};
	int eightDirectionsY[] = {-64, -64, -64,  0, 64, 64,  64,   0};
	
	for(int i = 0; i < 8; i++)
		addNeighbor(openList, closedList, entNode, entPtr, Vector2f( eightDirectionsX[i], eightDirectionsY[i] ) );

}


bool AI_Brain::closeEnough(const Vector2f &dest, IEntity* entPtr)
{
	return (abs(Distance( entPtr->m_physObject.GetPosition(), dest ) ) < 8);
}

bool AI_Brain::PlayerInView(int face, const Vector2f &playerLoc, const Vector2f &entLoc)
{
	if(face == NORTH)
		return (playerLoc.y < entLoc.y);

	if(face == SOUTH)
		return (playerLoc.y > entLoc.y);

	if(face == EAST)
		return (playerLoc.x > entLoc.x);

	if(face == WEST)
		return (playerLoc.x < entLoc.x);

	return false;
}

void AI_Brain::addEntity(IEntity* ptr)
{
	AI_Data data;
	data.awareOfPlayer = false;
	data.patrolIndex = 0;
    // TODO - read patrol path from a .BOT file
	data.patrol.insert(data.patrol.begin(),Vector2f(200,100) );
	data.patrol.insert(data.patrol.begin(),Vector2f(200,400) );
	m_dataTable[ ptr ] = data;
	entList.push_front(ptr);
}

void AI_Brain::RemoveEntity( IEntity *ent )
{
	m_dataTable.erase( ent );
}

// MOVE THIS TO THE AI_MNG.
void AI_Brain::controlEntities()
{
	std::list<IEntity*>::iterator iter;

	for(iter = entList.begin(); iter != entList.end(); ++iter)
		think(*iter);
		
}

void AI_Brain::checkStuck(IEntity* entPtr)
{
	AI_Data &data = m_dataTable[entPtr];
    if(data.lastLocation == entPtr->m_physObject.GetPosition())
	{
		data.path.clear();
		fillPath(entPtr, 64);
	}
}

//decides what action the entity should take
void AI_Brain::think(IEntity* entPtr)
{

	AI_Data &data = m_dataTable[entPtr];

	//the entity we will manipulate
	IEntity* playerPtr = EntityManager::GetInstance()->GetPlayer();

	float distance = entPtr->m_physObject.GetPosition().Distance( playerPtr->m_physObject.GetPosition() );

	if(PlayerInView( entPtr->GetFace(), playerPtr->m_physObject.GetPosition(), entPtr->m_physObject.GetPosition() ) && distance < 256)
		data.awareOfPlayer = true;

	if(distance < 64 || distance > 256 || !data.awareOfPlayer)
	{
		entPtr->m_actions = 0;
		if(data.patrol.size() > 0 && !data.awareOfPlayer)
			continuePatrol(entPtr);
		return;
	}

	checkStuck(entPtr);
	goToPlayer(entPtr);
	
}

void AI_Brain::continuePatrol(IEntity* entPtr)
{
	AI_Data &data = m_dataTable[entPtr];
	walkTo( entPtr->m_physObject.GetPosition(), data.patrol[data.patrolIndex], entPtr);
	if(closeEnough( data.patrol[data.patrolIndex], entPtr))
	{
		data.patrolIndex++;
		if(data.patrolIndex >= data.patrol.size())
			data.patrolIndex = 0;

		walkTo( entPtr->m_physObject.GetPosition(), data.patrol[data.patrolIndex], entPtr);
	}
	//m_dataTable[entPtr] = data;
}

void AI_Brain::goToPlayer(IEntity* entPtr)
{
	AI_Data &data = m_dataTable[entPtr];

	if(data.path.size() < 2)
	{
		data.path.clear();
		fillPath(entPtr,64);
	}
	else
	{
		if( !closeEnough(data.path[1],entPtr) )
		{
			walkTo(data.path[0],data.path[1],entPtr);
			data.lastLocation = entPtr->m_physObject.GetPosition();
		}
		else
		{
            data.path.pop_front();
			if(data.path.size() > 1)
				walkTo(data.path[0],data.path[1],entPtr);
			else
			{
				data.path.clear();
				fillPath(entPtr,64);
			}	
		}
	}
}

void AI_Brain::fillPath(IEntity* entPtr, int pixelsAway)
{

	AI_Data &data = m_dataTable[entPtr];

	//the list of nodes waiting to be explored
	std::list<PathNode> openList;
	//the list of nodes already explored
	std::list<PathNode> closedList;

	std::list<PathNode>::iterator iter;

	IEntity* playerPtr = EntityManager::GetInstance()->GetPlayer();

	Vector2f playerVector = playerPtr->m_physObject.GetPosition();
	Vector2f entVector = entPtr->m_physObject.GetPosition();

	//our goal is to reach the player
	PathNode goalNode( playerVector );
	//the root is the starting location
	PathNode rootNode( entVector );
	PathNode nullNode( Vector2f(-1000,-1000) );
	rootNode.entityPath = &nullNode;
	//put the root in the list of nodes to be explored
	openList.push_front(rootNode);

	//we have reached our goal when the location is within 100 of the player
	bool goalReached = !(Distance( goalNode, rootNode) > pixelsAway);

	PathNode* currentNode = &rootNode;

	//explore until the goal is reached
	while(!goalReached)
	{

		//find the node from the open list that is closest to the player
		for(iter = openList.begin(); iter != openList.end(); ++iter)
		{
			if(Distance( *currentNode, goalNode) > Distance( *iter, goalNode) )
			{
				currentNode = &(*iter);
                //break;
			}
		}
		//add the neighbors of the current node into the open list
		addNeighbors(openList, closedList, *currentNode, entPtr);
		//add the current node into the closed list
		closedList.push_front( *currentNode );
		//decide if we have reached our goal
		goalReached = !(Distance( goalNode, *currentNode) > pixelsAway && openList.size() > 0);
		//remove the current node from the open list
        //openList.erase( iter );
	}
	
	while(currentNode->location.x != -1000)
	{
        data.path.push_front( currentNode->location );
		currentNode = currentNode->entityPath;
	}
	
	data.path[0] = entPtr->m_physObject.GetPosition();
}

void AI_Brain::walkTo(const Vector2f current, const Vector2f destination, IEntity* entPtr)
{

	AI_Data &data = m_dataTable[entPtr];

	int button = 0;

	bool alignedX = false;
	bool alignedY = false;

	if(Distance(current,destination) > 64)
	{
		data.path.clear();
		fillPath(entPtr,64);
	}

	alignedX = abs(current.x - destination.x) < 5;
	alignedY = abs(current.y - destination.y) < 5;

	if(!alignedX)
	    if(current.x > destination.x)
			button |= BUTTON_LEFT;
		else
			button |= BUTTON_RIGHT;
	
	if(!alignedY)
		if(current.y < destination.y)
			button |= BUTTON_DOWN;
		else
			button |= BUTTON_UP;

	//if(alignedX && alignedY)
	//	button |= BUTTON_ATTACK1;

	entPtr->m_actions = button;

}

/** Handle an event */
bool AI_Brain::HandleEvent( IEvent* e )
{
	bool success = false;
	switch( e->GetType() )
	{
	case EVT_ADD_ENTITY:
        //IEntity* ent = e->GetData<EntityAdditionData>()->m_entity;
        if ( e->GetData<EntityAdditionData>()->m_entity->m_attributes.Has( "ai_type" ) ) // MAKE THIS WORK
        {
		    addEntity( e->GetData<EntityAdditionData>()->m_entity );
		    success = true;
        }
		break;
	case EVT_RM_ENTITY:
        //IEntity* ent2 = e->GetData<EntityAdditionData>()->m_entity;
        if ( e->GetData<EntityAdditionData>()->m_entity->m_attributes.Has( "ai_type" ) )
        {
		RemoveEntity( e->GetData<EntityAdditionData>()->m_entity );
		success = true;
        }
		break;
	default:
		std::cout << "AI_Manager:: Could not handle event type: " << e->GetType() << "\n";
		break;
	};
	return success;

}